<?php
/*
 *
 * @package MoltX
 * @author $Author$
 * @version $Id$
 * @link $URL$
 * @copyright Copyright (c) 2005-2010, Matthew Oltendorf
 * @license http://opensource.org/licenses/gpl-license.php GNU Public License
 *
 */

/*
 * @ignore
 */
if ( ! defined ( 'blink' ) )
{
	// Access denied.
	header ( $_SERVER [ 'SERVER_PROTOCOL' ] . ' 403 Forbidden' );
	echo '403 - Forbidden';

	exit;
}

/*
 * Handler role class
 * @package Blink
 *
 * Singleton
 */
final class blink_role
{
	private static	$active = 0, // The amount of acive rooms we are maintaining.
					$handlers = array ( ), // An indexed array of remote room handlers.
					$rooms = array ( ); // An indexed array of local or remote room servers.

	/*
	 * Constructor function
	 *
	 * This is private to enforce singleton mode.
	 *
	 * @return null
	 */
	private function __construct ( )
	{
		return null;
	}

	/*
	 * Initiate function
	 *
	 * Start the role.
	 *
	 * @return null
	 */
	public static function initiate ( )
	{
		// Development only. @todo: Remove for production.
		blink_console :: log ( 'role spawned' );

		// Load our server module.
		include blink_system . '/server/' . blink :: $storage . '.php';

		// Development only. @todo: Remove for production.
		blink_console :: log ( 'loaded modules' );

		// Get all rooms that are in the sockets folder.
		$rooms = preg_grep ( '/^([0-9a-zA-Z_-]+)\\.room.sock$/', scandir ( blink_storage . '/socket' ) );

		for ( $i = 0, $total = count ( $rooms ); $i < $total; ++$i )
		{
			$room = new blink_room ( $rooms [ $i ] );

			// Ping the room.
			if ( $room -> ping ( ) )
			{
				// Add the room to our list.
				self :: $rooms [ $room -> index ] = $room;
			}
		}

		// Are we the global handler?
		if ( blink :: $handler )
		{
			// @todo: Connect to global handler.
		}
		else
		{
			// Development only. @todo: Remove for production.
			blink_console :: log ( 'global handler = true' );
		}

		// @todo: Change to a higher interval for production.
		blink_server :: listen ( 'handler', false, 1, array ( __CLASS__, 'response' ) );

		// Development only. @todo: Remove for production.
		blink_console :: log ( 'role closed' );

		return null;
	}

	/*
	 * Response function
	 *
	 * Start the role.
	 *
	 * @param object $client the communication object
	 *
	 * @return boolean
	 */
	public static function response ( $client )
	{
		// Development only. @todo: Remove for production.
		blink_console :: log ( 'client acquired' );

		try
		{
			$data = $client -> get ( );
		}
		catch ( Exception $exception )
		{
			// Bad connection, ignore.

			// Development only. @todo: Remove for production.
			blink_console :: log ( 'Failed to fetch data' );

			return true;
		}

		try
		{
			// Development only. @todo: Remove for production.
			blink_console :: log ( 'got data from client' );

			if ( ! is_array ( $data ) )
			{
				throw new Exception ( 'INVALID DATA' );
			}
		}
		catch ( Exception $exception )
		{
			// Bad input, ignore.

			// Development only. @todo: Remove for production.
			blink_console :: log ( 'Invalid Data: ' . $input );

			return true;
		}

		$output = array ( );

		$close = false;

		if ( isset ( $data [ 'actions' ] ) )
		{
			try
			{
				if ( is_array ( $data [ 'actions' ] ) )
				{
					$actions = &$data [ 'actions' ];
				}
				else
				{
					$actions = array ( &$data [ 'actions' ] );
				}

				for ( $i = 0, $total = count ( $actions ); $i < $total; ++$i )
				{
					switch ( $actions [ $i ] )
					{
						case 'close':
							$close = true;
						break;

						default:
							throw new Exception ( 'INVALID ACTION' );
						break;
					}
				}
			}
			catch ( Exception $exception )
			{
				// Bad input, ignore.

				// Development only. @todo: Remove for production.
				blink_console :: log ( 'Invalid Data: ' . $input );
			}
		}

		if ( isset ( $data [ 'handler' ] ) )
		{
			try
			{
				if ( ! $data [ 'handler' ] instanceof blink_handler )
				{
					throw new Exception ( 'INVALID HANDLER' );
				}

				if ( $data [ 'handler' ] -> status != 'close' )
				{
					if ( ! isset ( self :: $handlers [ $data [ 'handler' ] -> index ] ) )
					{
						// Add this handler to our records. This is the first time we've received data from it.
						self :: $handlers [ $data [ 'handler' ] -> index ] = $data [ 'handler' ];
					}

					// Get a direct reference to the handler on our records, and get a copy of the incoming rooms data.
					$handler = self :: $handlers [ $data [ 'handler' ] -> index ];
					$rooms = $data [ 'handler' ] -> rooms;

					if ( ! is_array ( $rooms ) )
					{
						$rooms = array ( $rooms );
					}

					// Clear handler data, we already have a copy to work with.
					$data [ 'handler' ] -> active = 0;
					$data [ 'handler' ] -> rooms = array ( );

					// Add the rooms onto our array.
					foreach ( $rooms as $room )
					{
						try
						{
							if ( ! $room instanceof blink_room )
							{
								throw new Exception ( 'INVALID ROOM' );
							}

							if ( $room -> status == 'close' )
							{
								// The room is closing. Remove it from our records.
								if ( isset ( $handler -> rooms [ $room -> index ] ) )
								{
									--$handler -> active;

									unset ( self :: $rooms [ $room -> index ], $handler -> rooms [ $room -> index ] );
								}

								continue;
							}

							if ( ! isset ( self :: $rooms [ $room -> index ] ) )
							{
								// Add the room to our records.
								++$handler -> active;

								self :: $rooms [ $room -> index ] = $room;
								$handler -> rooms [ $room -> index ] = $room;
							}
							else
							{
								// We already have a room with this identifier active. Inform the duplicate on another server controlled by another handler to close up shop.
								$room -> close ( );
							}
						}
						catch ( Exception $exception )
						{
							// Bad input, ignore.

							// Development only. @todo: Remove for production.
							blink_console :: log ( 'Invalid Data In Handler: ' . $handler -> index );
						}
					}
				}
				else
				{
					// Don't know why a handler would tell us it's closing when we don't even have record of it...
					if ( isset ( self :: $handlers [ $data [ 'handler' ] -> index ] ) )
					{
						// Get a direct reference to the handler, and remove it from our records.
						$handler = self :: $handlers [ $data [ 'handler' ] ];
						unset ( self :: $handlers [ $handler -> index ] );

						foreach ( $handler -> rooms as $room )
						{
							// Remove each room it was managing from our records.
							unset ( self :: $rooms [ $room -> index ] );

							// Tell this room to close; but the handler that controlled them should have done this for us.
							$room -> close ( );
						}
					}
				}
			}
			catch ( Exception $exception )
			{
				// Bad input, ignore.

				// Development only. @todo: Remove for production.
				blink_console :: log ( 'Invalid Data: ' . $input );
			}
		}
		else if ( isset ( $data [ 'rooms' ] ) )
		{
			try
			{
				if ( is_array ( $data [ 'rooms' ] ) )
				{
					$rooms = &$data [ 'rooms' ];
				}
				else
				{
					$rooms = array ( &$data [ 'rooms' ] );
				}

				for ( $i = 0, $total = count ( $rooms ); $i < $total; ++$i )
				{
					// First, check if the room is already active.
					if ( ! isset ( self :: $rooms [ $rooms [ $i ] ] ) )
					{
						try
						{
							// Spawn room.
							if ( ! blink_room :: validate ( $rooms [ $i ] ) || ! self :: spawn ( $rooms [ $i ] ) )
							{
								continue;
							}
						}
						catch ( Exception $exception )
						{
							continue;
						}
					}

					$output [ 'rooms' ] [ $rooms [ $i ] ] = self :: $rooms [ $rooms [ $i ] ];
				}
			}
			catch ( Exception $exception )
			{
				// Bad input, ignore.

				// Development only. @todo: Remove for production.
				blink_console :: log ( 'Invalid Data: ' . $input );
			}
		}

		// Development only. @todo: Remove for production.
		blink_console :: log ( 'responding to client' );

		$client -> send ( $output );

		return ! $close;
	}

	/*
	 * Spawn room function
	 *
	 * Spawn an inactive room.
	 *
	 * @param string $index the room to spawn (in base 64)
	 *
	 * @return boolean
	 */
	private static function spawn ( $index )
	{
		$handler = null;
		$room = new blink_room ( $index );

		// Check if there's any remote handlers to use.
		if ( count ( self :: $handlers ) )
		{
			// @todo: find a desireable handler.
		}

		if ( $room -> spawn ( $handler ) )
		{
			// Add this newly created room to our records.
			self :: $rooms [ $index ] = $room;

			return true;
		}

		return false;
	}
}
